package pdm;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import lpsolve.LpSolve;
import lpsolve.LpSolveException;
import uil.Display;
import dea.Dea;
import dea.DeaSolution;
import dea.Dmu;

public class Pdm {
	public static int currItem = 0;
	static double currAlpha = 0.0;
	static String inputfilename = null;
	public static Dea dea = null;
	static List<StoreItem> store;
	
	public static void setInputFileName(String filename) throws Exception{
		Pdm.inputfilename = filename;
		dea = new Dea(inputfilename);
	}
	public static double[] getURMWeights(){
		double[] urmWeights = new double[dea.dmulist.get(currItem).Outputs.size()];
		for (int out = 0; out < dea.dmulist.get(currItem).Outputs.size(); out++) {
			urmWeights[out] =dea.dmulist.get(currItem).Outputs.get(out).OutputWeight;
		}
		return urmWeights;
	}
	public static double[] getDEAWeights(){
		DeaSolution deaSol = dea.CalcDEA(currItem);
		return deaSol.variables;
	}
	public static void Initialize(int dmu){
		Pdm.currItem = dmu;
		store = new ArrayList<StoreItem>();
		currAlpha = 0.0;
		try {
			Pdm.fillStore();
			Display.transitionlist = Pdm.getTransitionList();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void test() throws Exception{
		Dea dea = new Dea(inputfilename);
		DeaSolution deaSol = dea.CalcDEA(currItem);
		currAlpha = 5;
		print("currAlpha " + currAlpha);
		try {
			double[] vars = calcPdm(currAlpha, dea, deaSol, currItem, false);
			print("Flaeche " + getProfileDistance(currAlpha));
			for(int i=0;i<vars.length;i++){
				//print("var" + i + " " + vars[i]);
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		/*try {
			LpSolve lp = LpSolve.readLp("C:\\Users\\Niki\\workspace\\Data\\plaindeaV3.lp", 1, null);
			lp.solve();
			double[] test = new double[lp.getNcolumns()];
			lp.getVariables(test);

			for(int g = 0; g< test.length; g++)
			{
				Pdm.print("var" + g + " " +test[g]);
			}
			Pdm.print(lp.getObjective());
		} catch (LpSolveException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
	}
	public static void showNextProfile(int direction){
		//direction: 1 is down, 2 is up
		//Dea dea = new Dea(inputfilename);
		DeaSolution deaSol = dea.CalcDEA(currItem);
		//print("currAlpha " + currAlpha);
		try{
			double[] pdmResult = null;
			for(int i = 0; i < Display.transitionlist.size(); i++){
				if(direction==1){
					if(Pdm.currAlpha == Display.transitionlist.get(i).alpha){
						//print("index " + i);
						Pdm.currAlpha = Display.transitionlist.get(i-1).alpha;
						//print("newAlpha " + currAlpha);
						pdmResult = calcPdm(currAlpha, dea, deaSol, currItem, false);
						break;
					}
				}
				else{
					if(Pdm.currAlpha == Display.transitionlist.get(i).alpha){
						Pdm.currAlpha = Display.transitionlist.get(i+1).alpha;
						pdmResult = calcPdm(currAlpha, dea, deaSol, currItem, false);
						//print("newAlpha " + currAlpha);
						break;
					}
				}
			}
			//print("pdm is null " + (pdmResult==null));
			//print("transitionlist.size() " +Display.transitionlist.size());
			Display.SetPDMLine(pdmResult);
			Display.SetDistanceCount(calcDifference(dea, pdmResult), currAlpha);
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
	public static void fillStore() throws Exception{
		
		
		//add base item
		double lastflaeche = store(0);
		//print("Initial: 0\t" + lastflaeche);
		StoreItem newBase = new StoreItem(0, lastflaeche);
		int a = 1;
		print("FlaechenLoop "  + a++ + " Flaeche: " + newBase.flaeche);
		while(newBase.flaeche > 0.0001 && a <10){
			newBase = findNextTransition(newBase.flaeche, newBase.alpha);
			print("FlaechenLoop "  + a++ + " Flaeche: " + newBase.flaeche);
		}
		Collections.sort(store);
		
		
	}
	private static List<StoreItem> getTransitionList(){
		List<StoreItem> transitionlist = new ArrayList<StoreItem>(); 
		double lastsize = store.get(0).flaeche;
		transitionlist.add(store.get(0));
		for(int i = 0; i<store.size();i++){
			if(store.get(i).flaeche != lastsize){
				lastsize = store.get(i).flaeche;
				transitionlist.add(store.get(i));
			}
		}
		Collections.sort(transitionlist);
		for(int i = 0; i<transitionlist.size();i++){
			print("Alpha: " + transitionlist.get(i).alpha + "\tFlaeche " + transitionlist.get(i).flaeche);
		}
		return transitionlist;
	}
	private static StoreItem findNextTransition(double lastflaeche, double nextbase) throws Exception{
		StoreItem nextBaseItem;
		double step = 0.05;
		double currentflaeche = lastflaeche;
		
		//iterate using step until new flaeche is reached
		int u = 1;
		while(lastflaeche == currentflaeche && u < 10){
			print("while "  + u++ + " base " + nextbase);
			nextbase += step;
			currentflaeche = store(nextbase);
		}
		//print("NewBase: " + nextbase + "\t" + currentflaeche);
		nextBaseItem = new StoreItem(nextbase, currentflaeche);
		lastflaeche = currentflaeche;
		
		boolean direction = false; //direction indicator: false is down, true is up
		nextbase = nextbase-step/2;
		//print("a " + nextbase);
		if(store(nextbase) == lastflaeche){
			direction = false;
		}
		else{
			direction = true;
		}		
		double smallstep = step/4;
		double lastround = currentflaeche;
		//print("lastround " + lastround);
		for (int i =0;i <30;i++){
			//print("Loop "  + i + " base " + nextbase);
			if(direction){
				nextbase = nextbase + smallstep;
				currentflaeche = store(nextbase);
			}
			else{
				nextbase = nextbase - smallstep;
				currentflaeche = store(nextbase);
			}			
			if(currentflaeche == lastflaeche){
				direction = false;
				//print("b " + nextbase);
			}
			else{
				direction = true;			
			}
			//print("currentflaeche " + currentflaeche);
			if(currentflaeche == lastround){
				//print("reducing stepsize");
				smallstep /= 2;
			}
		}
		return nextBaseItem;
	}	
	private static double store(double alpha) throws Exception{
		StoreItem t = new StoreItem(alpha, getProfileDistance(alpha));
		//print("Store: " + t.alpha + "\t" + t.flaeche);
		store.add(t);
		return t.flaeche;
		 
	}
	/**
	 * Calculate PDM using alpha input and return profile distance value
	 * alpha = 0 = DEA, alpha => infinity = URM
	 * @param alpha
	 * @return
	 * @throws Exception 
	 */
	private static double getProfileDistance(double alpha) throws Exception{
		DeaSolution deaSol = dea.CalcDEA(currItem);		
		return calcDifference(Pdm.dea, calcPdm(alpha, Pdm.dea, deaSol, currItem, false));
	}
	private static double calcDifference(Dea dea, double[] pdmValues){
		double dist = 0.0;
		double tmp = 0.0; 
		 for (int z = 0; z < pdmValues.length - dea.dmulist.get(currItem).Inputs.size(); z++) {
			 tmp = dea.dmulist.get(currItem).Outputs.get(z).getPdmDirection() * 
			 (pdmValues[z]- dea.dmulist.get(currItem).Outputs.get(z).OutputWeight * pdmValues[0]);
			 dist += tmp;
			//print(dea.dmulist.get(currItem).Outputs.get(z).getPdmDirection() + " * " 
			//		 + " ( " +  pdmValues[z] + " - " + dea.dmulist.get(currItem).Outputs.get(z).OutputWeight + " * " 
			//		 + pdmValues[0] + ") = " + tmp); 
		 }
		 return dist;
	}
	private static double[] calcPdm(double alpha, Dea dea, DeaSolution deaSol,
			int i, boolean checkAllDirections) throws Exception {

		// Set PdmDirection foreach Output
		if (checkAllDirections) {
			
		} else {
			for (int out = 0; out < dea.dmulist.get(i).Outputs.size(); out++) {
				if (deaSol.variables[out] / deaSol.variables[0] > dea.dmulist
						.get(i).Outputs.get(out).OutputWeight) {
					dea.dmulist.get(i).Outputs.get(out).setPdmDirection(1);
				} else {
					dea.dmulist.get(i).Outputs.get(out).setPdmDirection(-1);
				}
				//print(" " + dea.dmulist.get(i).Outputs.get(out).getPdmDirection());
			}
			/*int[] n = new int[]{-1,-1,1,1,1,1,1,-1};
			for(int out = 0; out < dea.dmulist.get(i).Outputs.size(); out++){
				dea.dmulist.get(i).Outputs.get(out).setPdmDirection(n[out]);
				//Pdm.print("dir: " + dea.dmulist.get(i).Outputs.get(out).getPdmDirection());
			}*/
		}
		// Start PDM Calculation

		LpSolve lp = constructLPModel(dea.dmulist, i, alpha);

		lp.setVerbose(LpSolve.IMPORTANT);
		//lp.writeLp("PDM_model_" + i + ".lp");
		lp.solve();

		double[] test = new double[lp.getNcolumns()];
		lp.getVariables(test);

		for(int g = 0; g< test.length; g++)
		{
	    	test[g] = RoundUp(test[g]);
	    	//print(test[g]);
		}

		return test;
	}

	private static LpSolve constructLPModel(List<Dmu> dmulist, int currDmu,
			double alpha) throws LpSolveException {
		LpSolve lp;
		int j = 0;
		int Ncol = dmulist.get(0).Outputs.size() + dmulist.get(0).Inputs.size();

		int[] colNo = new int[Ncol];
		double[] row = new double[Ncol];

		lp = LpSolve.makeLp(0, Ncol);
		if (lp.getLp() == 0) {
			print("Could not generate new LP model");
			return null;
		}

		for (int i = 1; i <= Ncol; i++)
			lp.setColName(i, "u" + i);

		// add obj function
		j = 0;
		double tempVal = -1;
		for (int x = 0; x < dmulist.get(currDmu).Outputs.size(); x++) {

			tempVal = dmulist.get(currDmu).Outputs.get(x).OutputVal - alpha
					* dmulist.get(currDmu).Outputs.get(x).getPdmDirection();

			if (x == 0) {
				for (int cnt = 0; cnt < dmulist.get(currDmu).Outputs.size(); cnt++) {
					tempVal += alpha
							* (dmulist.get(currDmu).Outputs.get(cnt)
									.getPdmDirection() * dmulist.get(currDmu).Outputs
									.get(cnt).OutputWeight);
					//print("x" + cnt + "_tempVal: " + tempVal);
				}
			}
			row[x+1] = tempVal;
		}

		lp.setObjFn(row);

		lp.setAddRowmode(true);

		// add denominator equality constraint
		j = 0;
		for (int x = dmulist.get(currDmu).Outputs.size() + 1; x <= dmulist
				.get(currDmu).Outputs.size()
				+ dmulist.get(currDmu).Inputs.size(); x++) {
			colNo[j] = x;
			row[j++] = dmulist.get(currDmu).Inputs.get(x
					- dmulist.get(currDmu).Outputs.size() - 1).InputVal;
		}
		lp.addConstraintex(j, row, colNo, LpSolve.EQ, 1);

		// add GE constraints
		for (int dmu = 0; dmu < dmulist.size(); dmu++) {

			j = 0;
			for (int x = 1; x <= dmulist.get(dmu).Outputs.size(); x++) {
				colNo[j] = x;
				row[j++] = dmulist.get(dmu).Outputs.get(x - 1).OutputVal*-1;
				// lp.setLowbo(x, 0.02);
			}
			for (int x = 1; x <= dmulist.get(dmu).Inputs.size(); x++) {
				colNo[j] = x + dmulist.get(dmu).Outputs.size();
				row[j++] = dmulist.get(dmu).Inputs.get(x - 1).InputVal;
			}

			lp.addConstraintex(j, row, colNo, LpSolve.GE, 0);
		}

		// add PDM constraints
		if(alpha > 0){
			colNo = new int[Ncol];
			row = new double[Ncol];
	
			for (int x = 1; x < dmulist.get(currDmu).Outputs.size(); x++) {
				row = new double[Ncol];
				/*if (x == 0) {
					row[1] = (alpha * dmulist.get(currDmu).Outputs.get(x)
							.getPdmDirection())
							- (alpha
									* dmulist.get(currDmu).Outputs.get(x)
											.getPdmDirection() * dmulist
									.get(currDmu).Outputs.get(x).OutputWeight);
				} else*/ {
					row[x + 1] = alpha
							* dmulist.get(currDmu).Outputs.get(x).getPdmDirection();
					row[1] = alpha
							* dmulist.get(currDmu).Outputs.get(x).getPdmDirection()
							* dmulist.get(currDmu).Outputs.get(x).OutputWeight * -1;
				}
				lp.addConstraint(row, LpSolve.GE, 0);
			}
		}

		// limit variation of variable values to 1/10
		for (int x = 1; x <= dmulist.get(currDmu).Outputs.size() - 1; x++) {
			for (int z = 1; z <= dmulist.get(currDmu).Outputs.size(); z++) {
				if(z != x){
					row = new double[Ncol];
					row[x] = -0.1;
					row[z] = 1;
					lp.addConstraint(row, LpSolve.GE, 0);
				}

				//row = new double[Ncol];
				//row[x] = -10.1;
				//row[z] = 1;
				//lp.addConstraint(row, LpSolve.LE, 0);
			}
		}

		lp.setAddRowmode(false);
		/* set the object direction to maximize */
		lp.setMaxim();

		return lp;

	}
	public static double RoundUp(double val){
		return RoundUp(val, 10);
	}
	public static double RoundUp(double val, int precision){
    	BigDecimal bd = new BigDecimal(val);
    	bd = bd.setScale(precision,BigDecimal.ROUND_UP);
    	return bd.doubleValue();
		//return val;
	}
	public static void print(Double text) {
		System.out.println(text);
	}

	public static void print(String text) {
		System.out.println(text);
	}
	private static void print(int z) {
		System.out.println(z);
		
	}
}
